package edu.neu.cs6515.board;

import com.google.common.collect.ImmutableList;

import edu.neu.cs6515.util.Funct;
import edu.neu.cs6515.xml.IngeniousColor;
import edu.neu.cs6515.xml.Placement;
import edu.neu.cs6515.xml.Score;

/**
 * Calculates the score for an indidual placment of a tile
 */
public final class ScoreCalc {

    public static final int BONUS_SCORE_LINE = 18;
    private final HexagonBoard board;

    /**
     * Init class with board obj
     * 
     * @param board
     *            the board to be used for validation
     */
    public ScoreCalc(final HexagonBoard board) {
        this.board = board;
    }

    /**
     * @param p
     *            placement to be scored
     * @return Score made by Placement
     */
    public Score scoreAMove(final Placement p) {
        return Score.sum(ImmutableList.of(
                scoreAHexagon(new BoardCoord(p.getD0(), p.getA0())),
                scoreAHexagon(new BoardCoord(p.getD1(), p.getA1()))));
    }

    /**
     * Returns the indvidual score of placing a hexagon
     * 
     * @param boardCoords
     *            coordinate of the hexagon
     * @return int score for a placement, bigger or equal to zero
     */
    private Score scoreAHexagon(final BoardCoord boardCoords) {
        final Hexagon hexOnBoard = board.getHexagonAt(
                boardCoords.getDistance(), boardCoords.getAngle());
        final Score scoreTop = getScoreInDirection(hexOnBoard,
                HexagonDirection.TOP);
        final Score scoreTopRight = getScoreInDirection(hexOnBoard,
                HexagonDirection.TOPRIGHT);
        final Score scoreBottomRight = getScoreInDirection(hexOnBoard,
                HexagonDirection.BOTTOMRIGHT);
        final Score scoreBottom = getScoreInDirection(hexOnBoard,
                HexagonDirection.BOTTOM);
        final Score scoreBottomLeft = getScoreInDirection(hexOnBoard,
                HexagonDirection.BOTTOMLEFT);
        final Score scoreTopLeft = getScoreInDirection(hexOnBoard,
                HexagonDirection.TOPLEFT);
        return Score.sum(ImmutableList.of(scoreTop, scoreTopRight,
                scoreBottomRight, scoreBottom, scoreBottomLeft, scoreTopLeft));
    }

    /**
     * Calculates the score by following hexagons of the same color in the given
     * direction
     * 
     * @param neighbor
     *            Neighboring hexagon
     * @param direction
     *            implementation of a Funct<Hexagon, Hexagon> that gets the
     *            neighbor in a certain direction
     * 
     * @return score achieved by the same color in this direction (>= 0)
     */
    private Score getScoreInDirection(final Hexagon neighbor,
            final Funct<Hexagon, Hexagon> direction) {
        final Hexagon nextHex = direction.exec(neighbor);
        if (nextHex == null) {
            return new Score();
        }
        return collectPossiblePointsInDirection(direction, nextHex,
                nextHex.getColor());
    }

    /**
     * Navigates on the board in the specified direction and counts all hexagons
     * of the same color until a white one appears
     * 
     * @param direction
     *            can be used to get the next hex in this direction
     * @param neighbor
     *            startNeighbor
     * @param startColor
     *            color to be counted
     * @return new Score containing all points of a color in the given direction
     */
    private Score collectPossiblePointsInDirection(
            final Funct<Hexagon, Hexagon> direction, final Hexagon neighbor,
            final IngeniousColor startColor) {
        Hexagon nextHex = neighbor;
        final Score scoreInDirection = new Score();
        while (nextHex != null) {
            if (startColor != IngeniousColor.WHITE
                    && nextHex.getColor().equals(startColor)) {
                scoreInDirection.addPointForColor(startColor);
                nextHex = direction.exec(nextHex);
            } else {
                break;
            }
        }
        return scoreInDirection;
    }
}
